﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using MCServerHelper.Properties;
using System.Timers;

namespace MCServerHelper
{
    public class MinecraftServer : IDisposable
    {
        private StreamWriter _input = null;
        private Process _minecraft = null;
        private Timer _notificationTimer = null;

        // State variables
        private bool _isStopWaitingForSave = false;
        private bool _isKickWaitingForList = false;

        private const string SAVE_MESSAGE = "[INFO] CONSOLE: Save complete.";
        private const string LIST_MESSAGE = "Connected players: ";
        private const string PLAYER_COUNT_MESSAGE = "Player count: ";

        public event EventHandler<EventArgs<string>> OutputReceived;
        public event EventHandler<EventArgs> StopCompleted;

        private readonly List<IMinecraftService> _services = new List<IMinecraftService>();
        public List<IMinecraftService> Services
        {
            get { return _services; }
        }

        public MinecraftServer() 
        {
            InitializeTimer();
        }

        #region Public Methods

        public void Start()
        {
            // Fire up minecraft through java,
            // redirecting console input and output to us.
            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardOutput = true;
            startInfo.RedirectStandardInput = true;
            startInfo.RedirectStandardError = true;
            startInfo.CreateNoWindow = true;
            startInfo.FileName = string.Format("\"{0}\"", Settings.Default.JavaPath);
            startInfo.Arguments = string.Format("-Xmx1024M -Xms1024M -jar \"{0}\" nogui", Settings.Default.MinecraftServerPath);
            startInfo.WorkingDirectory = new FileInfo(Settings.Default.MinecraftServerPath).Directory.FullName;

            _minecraft = new Process();
            _minecraft.StartInfo = startInfo;

            _minecraft.Exited += new EventHandler(_minecraft_Exited);
            _minecraft.OutputDataReceived += new DataReceivedEventHandler(minecraft_OutputDataReceived);
            _minecraft.ErrorDataReceived += new DataReceivedEventHandler(minecraft_OutputDataReceived);

            _minecraft.Start();

            _input = _minecraft.StandardInput;
            _input.AutoFlush = true;

            // Read all output asynchronously.
            _minecraft.BeginOutputReadLine();
            _minecraft.BeginErrorReadLine();

            foreach (IMinecraftService service in Services)
            {
                service.Initialize(this);
                service.ServerStarted();
            }
        }

        public void BeginStop()
        {
            SendRebootNotification();
        }

        public void ForceStop()
        {
            // Don't save all, just stop
            EndStop();
        }

        public void KickPlayers(string[] players)
        {
            foreach (string player in players)
            {
                WriteInput(string.Format(Command.KickPlayer, player));
            }
        }

        public void WriteInput(string input)
        {
            if (_input != null)
            {
                _input.WriteLine(input);
            }
        }

        #endregion Public Methods

        #region Private Methods

        private void EndStop()
        {
            if (_input != null)
            {
                try
                {
                    _input.WriteLine(Command.Stop);
                    _input.Flush();
                    _input.Close();
                }
                finally
                {
                    _input.Dispose();
                    _input = null;
                }
            }

            if (_minecraft != null)
            {
                _minecraft.CancelErrorRead();
                _minecraft.CancelOutputRead();

                _minecraft.OutputDataReceived -= new DataReceivedEventHandler(minecraft_OutputDataReceived);
                _minecraft.ErrorDataReceived -= new DataReceivedEventHandler(minecraft_OutputDataReceived);

                if (!_minecraft.WaitForExit(1000))
                {
                    try
                    {
                        _minecraft.Kill();
                    }
                    catch (Exception)
                    {
                        //Do nothing
                    }
                }
                _minecraft.Dispose();
                _minecraft = null;
            }

            if (StopCompleted != null)
            {
                StopCompleted(this, EventArgs.Empty);
            }

            foreach (IMinecraftService service in Services)
            {
                service.ServerStopped();
            }
        }

        private void InitializeTimer()
        {
            // Notification timer lasts 2 minutes
            _notificationTimer = new Timer(120000);
            _notificationTimer.AutoReset = false;
            _notificationTimer.Elapsed += new ElapsedEventHandler(_notificationTimer_Elapsed);
        }

        private void SendRebootNotification()
        {
            WriteInput(string.Format(Command.SayMessaage, "Server will reboot in two minutes."));
            WriteInput(string.Format(Command.SayMessaage, "Get to a safe place and disconnect immediately."));
            WriteInput(string.Format(Command.SayMessaage, "Server will be available again a few minutes."));
            _notificationTimer.Start();
        }

        private void _notificationTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            WriteInput(string.Format(Command.SayMessaage, "Server rebooting..."));
            KickAllPlayers();
        }

        private void KickAllPlayers()
        {
            _isKickWaitingForList = true;
            WriteInput(Command.List);
        }

        private void ProcessOutput(string output)
        {
            if (output == null)
            {
                return;
            }

            if (OutputReceived != null)
            {
                OutputReceived(this, new EventArgs<string>(output));
            }

            LookForNewPlayer(output);

            if (_isStopWaitingForSave)
            {
                // Save completed, so lets finish our save event
                if (output.Contains(SAVE_MESSAGE))
                {
                    try
                    {
                        EndStop();
                    }
                    finally
                    {
                        _isStopWaitingForSave = false;
                    }
                }
            }

            if (_isKickWaitingForList)
            {
                if (output.Contains(LIST_MESSAGE))
                {
                    try
                    {
                        string[] players = ParsePlayerNames(output);
                        KickPlayers(players);
                        _isStopWaitingForSave = true;
                        _input.WriteLine(Command.SaveAll);
                    }
                    finally
                    {
                        _isKickWaitingForList = false;
                    }
                }
            }

            foreach (IMinecraftService service in Services)
            {
                service.ProcessOutput(output);
            }
        }

        private void LookForNewPlayer(string output)
        {
            string infoText = "[INFO] ";
            string loggedInText = " logged in";

            if (output.Contains(infoText) && output.EndsWith(loggedInText))
            {
                int infoIndex = output.IndexOf(infoText);
                int loggedIndex = output.IndexOf(loggedInText);
                int nameIpLength = output.Length - (infoIndex + infoText.Length + loggedInText.Length);
                string nameIp = output.Substring(infoIndex + infoText.Length, nameIpLength);

                string[] nameIpCollection = nameIp.Split(new char[] { ' ', '[', ']', '/' }, StringSplitOptions.RemoveEmptyEntries);
                if(nameIpCollection.Length == 2)
                {
                    foreach (IMinecraftService service in Services)
                    {
                        service.PlayerConnected(nameIpCollection[0], nameIpCollection[1]);
                    }
                }
            }
        }

        private string[] ParsePlayerNames(string message)
        {
            int index = message.IndexOf(LIST_MESSAGE);
            string[] players = message.Substring(index + LIST_MESSAGE.Length).Split(new char[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
            return players;
        }

        private void minecraft_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            ProcessOutput(e.Data);
        }

        private void _minecraft_Exited(object sender, EventArgs e)
        {
        }

        #endregion Private Methods

        public void Dispose()
        {
        }
    }
}

